package wortubung.inflection;

import static wortubung.util.CommonUtils.*;
import static wortubung.util.JSONUtils.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import wortubung.JSONConsts;
import wortubung.core.LANG;
import wortubung.util.TransliterateException;

public class ConjugationResult implements VerbConjugation {
	private transient boolean isLatinized = false;

	private String verb;
	private boolean isSuffixOnly;
	private boolean isVerified;
	private Integer refIndex1;
	private Integer refIndex2;
	private String  comment;
	private boolean isMeaning;
	private String[] usage;
	ArrayList<ConjugationSet> conjugations;

	public ConjugationResult(String verb) {
		this(verb, null, null, null, null);
	}

	public ConjugationResult(String verb, Integer refIndex1, Integer refIndex2) {
		this(verb, refIndex1, refIndex2, null, null);
	}

	public ConjugationResult(String verbe, ConjugationSet cs1, ConjugationSet cs2) {
		this(verbe, null, null, cs1, cs2);
	}

	public ConjugationResult(String verb, Integer refIndex1, Integer refIndex2, ConjugationSet cs1, ConjugationSet cs2) {
		this.verb = verb;
		this.refIndex1 = refIndex1;
		this.refIndex2 = refIndex2;

		conjugations = new ArrayList<ConjugationSet>(1);
		if (cs1 != null) conjugations.add(cs1);
		if (cs2 != null) conjugations.add(cs2);
	}

	@Override public String getVerb() { return verb; }
	@Override public boolean isSuffixOnly() { return isSuffixOnly; }
	@Override public boolean isRootSuffixOnly() { return false; }

	@Override public String getPrefix() { return null; }
	@Override public String getDerivedFrom() { return null; }

	public boolean isVerified() { return isVerified; }
	public boolean hasRefIndices() { return refIndex1 != null || refIndex2 != null; }
	@Override public Integer getRefIndex1() { return refIndex1; } 
	@Override public Integer getRefIndex2() { return refIndex2; }
	public void setRefIndex1(Integer idx) { refIndex1 = idx; }
	public void setRefIndex2(Integer idx) { refIndex2 = idx; }

	public String getMeaning() { return isMeaning ? comment : null; }
	public void setMeaning(String meaning) { this.comment = meaning; isMeaning = true; }
	public String getComment() { return isMeaning ? null : comment; }
	public void setComment(String comment) { this.comment = comment; isMeaning = false; }
	public String[] getUsage() { return usage; }
	public void setUsage(String[] usage) { this.usage = usage; }

	/**
	 * For regular verbs, typically there are numbered groups (or types) of conjugations.
	 */
	public int getGroup() { return (refIndex1 != null && refIndex1 < 0) ? -refIndex1 : 0; }

	public String getRefIndex1Name() {
		return getRefName(getRefIndex1());
	}
	
	public static String getRefName(Integer refIndx) {
		if (refIndx == null)
			return "";
		switch(refIndx) {
		case -1: return "I";
		case -2: return "II";
		case -3: return "III";
		default: return refIndx.toString();
		}
	}

	public int numConjugationSet() { return length(conjugations); }
	public ArrayList<ConjugationSet> getConjugations() { return conjugations; }
	public void addConjugationSet(ConjugationSet cs) { cs.setConjugationResult(this); conjugations.add(cs); }
	@Override public boolean hasAlternative() { return numConjugationSet() > 1; }

	public static ConjugationResult fromJSON(JSONObject json, LANG lang) throws JSONException, TransliterateException {
		if (json.optBoolean(JSONConsts.IGNORE.name()))
			return null;

		String verb = lang.toVernacular(json.getString(JSONConsts.VERB.name()));

    	Integer[] ia = getIntArray(json, JSONConsts.REF_INDICES.name());
		ConjugationResult ret = new ConjugationResult(verb, at(ia, 0), at(ia, 1));
    	ret.isVerified = json.optBoolean(JSONConsts.IS_VERIFIED.name());
    	
		String comment = getString(json, JSONConsts.COMMENT.name(), null);
		if (comment == null) {
			comment = getString(json, JSONConsts.MEANING.name(), null);
			ret.isMeaning = comment != null;
		}
		ret.setComment(comment);

		ret.setUsage(getStringArray(json, JSONConsts.USAGE.name()));

		Object conjugations = json.get(JSONConsts.CONJUGATION.name());
		if (conjugations instanceof JSONObject)
			ret.addConjugationSet(ConjugationSet.fromJSON(ret, (JSONObject)conjugations, lang));
		else if (conjugations instanceof JSONArray) {
			JSONArray arr = (JSONArray)conjugations;
			for (int i=0; i<arr.length(); ++i)
				ret.addConjugationSet(ConjugationSet.fromJSON(ret, arr.getJSONObject(i), lang));
		}

		return ret;
	}

	@Override
	public ConjugationResult getConjugationResult(LANG langForLatinizing) {
		if ((langForLatinizing == null) || isLatinized)
			return this;

		// Check this and create a latinized one if needed to
		String tmp = langForLatinizing.toLatin(this.verb, true);
		boolean hasDiffs = !eq(tmp, this.verb, true);
		ConjugationResult ret = new ConjugationResult(tmp, refIndex1, refIndex2);
		ret.isLatinized = true;
		ret.isVerified = this.isVerified;
		ret.isSuffixOnly = this.isSuffixOnly;
		ret.isMeaning = this.isMeaning;
		ret.comment = langForLatinizing.toLatin(this.comment, true);
		hasDiffs |= !eq(ret.comment, this.comment, true);
		ret.conjugations = new ArrayList<ConjugationSet>();
		for (ConjugationSet cs : this.conjugations) {
			ConjugationSet cs1 = cs.getLatinized(this, langForLatinizing);
			ret.conjugations.add(cs1);
			hasDiffs |= (cs1 != cs);
		}
		if (hasDiffs)
			return ret;
		
		this.isLatinized = true;
		return this;
	}

	public void checkError(ArrayList<String[]> errors, LANG lang, ConjugationResult toBeChecked) {
		int cnt1 = getConjugations().size();
		int cnt2 = toBeChecked.getConjugations().size();
		if (cnt1 != cnt2) {
			InflectionError e = new InflectionError(getVerb(), "Number of conjugations", null, cnt1, cnt2);
			errors.add(e.toStringArray(lang));
			return;
		}

		if (!hasAlternative()) {
			ArrayList<InflectionError> errs = conjugations.get(0).checkError(toBeChecked.conjugations.get(0));
			if (errs != null) {
				for (InflectionError e : errs)
					errors.add(e.toStringArray(lang));
			}
			return;
		}

		// Now we have multiple conjugations.
		String[][] saa1 = toSAA(getConjugations());
		String[][] saa2 = toSAA(toBeChecked.getConjugations());

		InflectionError e;
		if (saa1.length != saa2.length) {
			e = new InflectionError(getVerb(), "Unmatching conjugations", null, saa1.length, saa2.length);
			errors.add(e.toStringArray(lang));
			return;
		}

		for (int i=0; i<saa1.length; ++i) {
			String[] sa1 = saa1[i];
			String[] sa2 = saa2[i];
			if (compare(sa1[2], sa2[2]) != 0) {
				e = new InflectionError(getVerb(), sa1[0], isNotEmpty(sa1[1]) ? PERSON.valueOf(sa1[1]) : null, sa1[2], sa2[2]);
				errors.add(e.toStringArray(lang));
			}
		}
	}

	private static String[][] toSAA(ArrayList<ConjugationSet> conjs) {
		ArrayList<String[]> sal = new ArrayList<String[]>();
		for (ConjugationSet cs : conjs) {
			addToSAL(sal, "PRESENT_PARTICIPLE", "", cs.getPresentParticiple());
			addToSAL(sal, "PAST_PARTICIPLE", "", cs.getPastParticiple());
			for (CONJUGATION c : CONJUGATION.values()) {
				String[] sa = cs.get(c);
				if (sa == null)
					continue;
				addToSAL(sal, c.name(), PERSON.S1.name(), at(sa, 0));
				addToSAL(sal, c.name(), PERSON.S2.name(), at(sa, 1));
				addToSAL(sal, c.name(), PERSON.S3.name(), at(sa, 2));
				addToSAL(sal, c.name(), PERSON.P1.name(), at(sa, 3));
				addToSAL(sal, c.name(), PERSON.P2.name(), at(sa, 4));
				addToSAL(sal, c.name(), PERSON.P3.name(), at(sa, 5));
			}
		}

		String[][] saa = sal.toArray(new String[sal.size()][]);
		Comparator<String[]> cptr = new Comparator<String[]>() {
			@Override
			public int compare(String[] a, String[] b) {
				if (a.length > b.length)
					return 1;
				if (a.length < b.length)
					return -1;
				for (int i=0; i<a.length; ++i) {
					int x = wortubung.util.CommonUtils.compare(a[i], b[i]);
					if (x != 0)
						return x;
				}
				return 0;
			}
		};
		Arrays.sort(saa, cptr);
		return saa;
	}

	private static void addToSAL(ArrayList<String[]> sal, String type, String person, String value) {
		if (isNotEmpty(value))
			sal.add(new String[]{ type, person, value });
	}

	public ConjugationResult forPrefix(String prefix, DerivedVerbConjugation.PrefixProcessor prefixProcessor) {
		ConjugationResult ret = new ConjugationResult(prefix + verb, refIndex1, refIndex2);
		for (ConjugationSet cs : getConjugations())
			ret.addConjugationSet(prefixProcessor.process(prefix, cs));
		return ret;
	}

} // end of class ConjugationResult.
